/**
 * Collaborative Story Development Web Application (CSDApp) 
 * Copyright Anton Strack 2014
 *
 * This file is part of Collaborative Story Development Application (CSDApp).
 *
 * CSDApp is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or any later version.
 *
 * CSDApp is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * CSDApp. If not, see <http://www.gnu.org/licenses/>.
 *
 */
package csdwa;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This class provides access to the various type tables as well as the
 * translation abilities between the id value and the name value of the specific
 * role. This class object is intended to be populated on each call, and should
 * prevent the need for hardcoding any of the type id values.
 *
 * @author Anton Strack
 */
public class InfoAggregator {

    /**
     * The datasource where the information is stored.
     */
    private Datasource ds;
    /**
     * The primary map for resource types.
     */
    private Map<String, ResourceType> resourceTypes;
    /**
     * The primary map for action types.
     */
    private Map<String, Action> actionTypes;
    /**
     * The primary map for role types.
     */
    private Map<String, Role> roleTypes;
    /**
     * The primary map for state types
     */
    private Map<String, State> stateTypes;
    /**
     * The primary map for taskActions. TaskActions are different than just
     * actions in that they con contain combination actions. The information
     * aggregator provides methods for converting to and from taskActions and
     * Actions.
     */
    private Map<String, TaskAction> taskActionTypes;
    /**
     * This variable sets if the additional set of maps should be used or not.
     * Using them means a larger memory footprint but faster access by id
     * values. If this is set to false then every time an id value is used to
     * search for a name or bean, the respective map must be searched through
     * until a match is found, or the end if no match exists.
     */
    private boolean useIdMapping;
    /**
     * The id to string mapping of resource types. The value portion is intended
     * to be used as a key for the primary map.
     */
    private Map<Integer, String> resourceTypeIds;
    /**
     * The id to string mapping of action types. The value portion is intended
     * to be used as a key for the primary map.
     */
    private Map<Integer, String> actionTypeIds;
    /**
     * The id to string mapping of role types. The value portion is intended to
     * be used as a key for the primary map.
     */
    private Map<Integer, String> roleTypeIds;
    /**
     * The id to string mapping of action types. The value portion is intended
     * to be used as a key for the primary map.
     */
    private Map<Integer, String> stateTypeIds;
    /**
     * The id to string mapping of role types. The value portion is intended to
     * be used as a key for the primary map.
     */
    private Map<Integer, String> taskActionTypeIds;

    // private Map<String, State> stateTypes;
    /*
     * this singleton implementation was designed by Bill Pugh to overcome issues with java's implemenation of the singleton pattern.
     * this section of code was taken from http://sourcemaking.com/design_patterns/singleton/java/1
     */
    // Private constructor prevents instantiation from other classes
    private InfoAggregator() {
    }

    /**
     * @return the resourceTypes
     */
    public List<ResourceType> getResourceTypes() {
        return new ArrayList<ResourceType>(resourceTypes.values());
    }

    /**
     * @return the actionTypes
     */
    public List<Action> getActionTypes() {
        return new ArrayList<Action>(actionTypes.values());
    }

    /**
     * @return the roleTypes
     */
    public List<Role> getRoleTypes() {
        return new ArrayList<Role>(roleTypes.values());
    }

    /**
     * @return the stateTypes
     */
    public List<State> getStateTypes() {
        return new ArrayList<State>(stateTypes.values());
    }

    /**
     * @return the taskActionTypes
     */
    public List<TaskAction> getTaskActionTypes() {
        return new ArrayList<TaskAction>(taskActionTypes.values());
    }

    /**
     * SingletonHolder is loaded on the first execution of
     * Singleton.getInstance() or the first access to SingletonHolder.INSTANCE,
     * not before.
     */
    private static class SingletonHolder {

        private static final InfoAggregator INSTANCE = new InfoAggregator();
    }

    public static InfoAggregator getInstance() {
        return SingletonHolder.INSTANCE;
    }
    /* end singleton implementation code */

    public void initialize(Datasource ds, boolean useIdMapping) {
        this.ds = ds;
        // set the default for each type
        ResourceType rt = new ResourceType();
        rt.setId(0);
        rt.setName("Site");
        rt.setParentId(0);
        this.resourceTypes = new HashMap<String, ResourceType>();
        this.resourceTypes.put(rt.getName().toLowerCase(), rt);
        Action a = new Action();
        a.setId(0);
        a.setName("List");
        this.actionTypes = new HashMap<String, Action>();
        this.actionTypes.put(a.getName().toLowerCase(), a);
        Role role = new Role();
        role.setId(0);
        role.setName("Guest");
        this.roleTypes = new HashMap<String, Role>();
        this.roleTypes.put(role.getName().toLowerCase(), role);
        TaskAction ta = new TaskAction();
        ta.setId(0);
        ta.setName("Read");
        this.taskActionTypes = new HashMap<String, TaskAction>();
        this.taskActionTypes.put(ta.getName().toLowerCase(), ta);
        State state = new State();
        state.setId(0);
        state.setName("No state");
        this.stateTypes = new HashMap<String, State>();
        this.stateTypes.put(state.getName().toLowerCase(), state);

        this.useIdMapping = useIdMapping;
        if (useIdMapping == true) {
            this.resourceTypeIds = new HashMap<Integer, String>();
            this.resourceTypeIds.put(0, "site");
            this.actionTypeIds = new HashMap<Integer, String>();
            this.actionTypeIds.put(0, "list");
            this.roleTypeIds = new HashMap<Integer, String>();
            this.roleTypeIds.put(0, "guest");
            this.taskActionTypeIds = new HashMap<Integer, String>();
            this.taskActionTypeIds.put(0, "read");
            this.stateTypeIds = new HashMap<Integer, String>();
            this.stateTypeIds.put(0, "nostate");
        }
    }

    /**
     * Populate the resource types map with the values from the database.
     */
    public void populateResourceTypes() {
        ResourceTypeDao rtDao = new ResourceTypeDao(this.ds.getConnection());
        int result = rtDao.selectBeans();
        if (result > 0) {
            for (ResourceType rt : rtDao.getList()) {
                String name = prepareName(rt.getName());// remove all spaces between words
                this.resourceTypes.put(name, rt);
                if (this.useIdMapping == true) {
                    this.resourceTypeIds.put(rt.getId(), name);
                }
            }
        }
    }

    /**
     * Populate the action types map with the values from the database.
     */
    public void populateActionTypes() {
        ActionDao aDao = new ActionDao(this.ds.getConnection());
        int result = aDao.selectBeans();
        if (result > 0) {
            for (Action a : aDao.getList()) {
                String name = prepareName(a.getName());
                this.actionTypes.put(name, a);
                if (this.useIdMapping == true) {
                    this.actionTypeIds.put(a.getId(), name);
                }
            }
        }
    }

    /**
     * Populate the resource types map with the values from the database.
     */
    public void populateRoleTypes() {
        RoleDao rDao = new RoleDao(this.ds.getConnection());
        int result = rDao.selectBeans();
        if (result > 0) {
            for (Role r : rDao.getList()) {
                String name = prepareName(r.getName());
                this.roleTypes.put(name, r);
                if (this.useIdMapping == true) {
                    this.roleTypeIds.put(r.getId(), name);
                }
            }
        }
    }

    /**
     * Populate the taskAction types map with the values from the database.
     */
    public void populateTaskActionTypes() {
        TaskActionDao taDao = new TaskActionDao(this.ds.getConnection());
        int result = taDao.selectBeans();
        if (result > 0) {
            for (TaskAction ta : taDao.getBeans()) {
                String name = prepareName(ta.getName());
                this.taskActionTypes.put(name, ta);
                if (this.useIdMapping == true) {
                    this.taskActionTypeIds.put(ta.getId(), name);
                }
            }
        }
    }

    /**
     * Populate the state types map with the values from the database.
     */
    public void populateStateTypes() {
        StateDao sDao = new StateDao(this.ds.getConnection());
        int result = sDao.selectBeans();
        if (result > 0) {
            for (State s : sDao.getBeans()) {
                String name = prepareName(s.getName());
                this.stateTypes.put(name, s);
                if (this.useIdMapping == true) {
                    this.stateTypeIds.put(s.getId(), name);
                }
            }
        }
    }

    /**
     * Get the role object according to it's name.
     *
     * @param roleName the name of the role to return.
     * @return the role that matches the provided name or null if none exist.
     */
    public Role getRoleType(String roleName) {
        roleName = prepareName(roleName);
        if (this.roleTypes.containsKey(roleName)) {
            return this.roleTypes.get(roleName);
        }
        return null;
    }

    /**
     * Get the action object according to it's name.
     *
     * @param actionName the name of the action to return.
     * @return the action that matches the provided name or null if none exist.
     */
    public Action getActionType(String actionName) {
        actionName = prepareName(actionName);
        if (this.actionTypes.containsKey(actionName)) {
            return this.actionTypes.get(actionName);
        }
        return null;
    }

    /**
     * Return the resourcetype object according to it's name.
     *
     * @param resourceTypeName the name of the resourcetype to return.
     * @return the resourcetype that matches the provided name or null if none
     * exist.
     */
    public ResourceType getResourceType(String resourceTypeName) {
        resourceTypeName = prepareName(resourceTypeName);
        if (this.resourceTypes.containsKey(resourceTypeName)) {
            return this.resourceTypes.get(resourceTypeName);
        }
        return null;
    }

    /**
     * Get the state object according to it's name.
     *
     * @param stateName the name of the state to return.
     * @return the state that matches the provided name or null if none exist.
     */
    public State getStateType(String stateName) {
        stateName = prepareName(stateName);
        if (this.stateTypes.containsKey(stateName)) {
            return this.stateTypes.get(stateName);
        }
        return null;
    }

    /**
     * Get the task action object according to it's name.
     *
     * @param taskActionName the name of the task action to return.
     * @return the task action that matches the provided name or null if none
     * exist.
     */
    public TaskAction getTaskActionType(String taskActionName) {
        taskActionName = prepareName(taskActionName);
        if (this.taskActionTypes.containsKey(taskActionName)) {
            return this.taskActionTypes.get(taskActionName);
        }
        return null;
    }

    /**
     * Get the role object according to it's id.
     *
     * @param roleId the name of the role to return.
     * @return the role that matches the provided id or null if none exist.
     */
    public Role getRoleType(int roleId) {
        if (this.useIdMapping == true && this.roleTypeIds.containsKey(roleId)) {
            return this.getRoleType(this.roleTypeIds.get(roleId));
        } else if (this.useIdMapping == false) {
            for (Role r : this.roleTypes.values()) {
                if (r.getId() == roleId) {
                    return r;
                }
            }
        }
        return null;
    }

    /**
     * Get the action object according to it's id.
     *
     * @param actionId the name of the action to return.
     * @return the action that matches the provided id or null if none exist.
     */
    public Action getActionType(int actionId) {
        if (this.useIdMapping == true && this.actionTypeIds.containsKey(actionId)) {
            return this.getActionType(this.actionTypeIds.get(actionId));
        } else if (this.useIdMapping == false) {
            for (Action a : this.actionTypes.values()) {
                if (a.getId() == actionId) {
                    return a;
                }
            }
        }
        return null;
    }

    /**
     * Return the resourcetype object according to it's id.
     *
     * @param resoourceTypeId the name of the resourcetype to return.
     * @return the resourcetype that matches the provided id or null if none
     * exist.
     */
    public ResourceType getResourceType(int resoourceTypeId) {
        if (this.useIdMapping == true && this.resourceTypeIds.containsKey(resoourceTypeId)) {
            return this.getResourceType(this.resourceTypeIds.get(resoourceTypeId));
        } else if (this.useIdMapping == false) {
            for (ResourceType r : this.resourceTypes.values()) {
                if (r.getId() == resoourceTypeId) {
                    return r;
                }
            }
        }
        return null;
    }

    /**
     * Get the state object according to it's id.
     *
     * @param stateId the name of the stete to return.
     * @return the state that matches the provided id or null if none exist.
     */
    public State getStateType(int stateId) {
        if (this.useIdMapping == true && this.stateTypeIds.containsKey(stateId)) {
            return this.getStateType(this.stateTypeIds.get(stateId));
        } else if (this.useIdMapping == false) {
            for (State r : this.stateTypes.values()) {
                if (r.getId() == stateId) {
                    return r;
                }
            }
        }
        return null;
    }

    /**
     * Get the task action object according to it's id.
     *
     * @param taskActionId the name of the task action to return.
     * @return the task action that matches the provided id or null if none
     * exist.
     */
    public TaskAction getTaskActionType(int taskActionId) {
        if (this.useIdMapping == true && this.taskActionTypeIds.containsKey(taskActionId)) {
            return this.getTaskActionType(this.taskActionTypeIds.get(taskActionId));
        } else if (this.useIdMapping == false) {
            for (TaskAction a : this.taskActionTypes.values()) {
                if (a.getId() == taskActionId) {
                    return a;
                }
            }
        }
        return null;
    }

    /**
     * This method gets the resource type id of the resource according to its
     * name. It primarily differs from calling getResourceType by checking for
     * null values and returning the default resource if a null exists. Thus it
     * provides some protection against manually entered values into the url
     * queries.
     *
     * @param resourceTypeName The name of the resource type to get the id for.
     * @return the id value of the resource if it exists and 0 if it doesn't
     * exist.
     */
    public int getResourceTypeId(String resourceTypeName) {
        ResourceType type = this.getResourceType(resourceTypeName);
        if (type != null) {
            return type.getId();
        }
        return 0;
    }

    /**
     * This method gets the action type id of the action according to its name.
     * It primarily differs from calling getActionType by checking for null
     * values and returning the default action if a null exists. Thus it
     * provides some protection against manually entered values into the url
     * queries.
     *
     * @param actionTypeName The name of the action type to get the id for.
     * @return the id value of the action if it exists and 0 if it doesn't
     * exist.
     */
    public int getActionTypeId(String actionTypeName) {
        Action type = this.getActionType(actionTypeName);
        if (type != null) {
            return type.getId();
        }
        return 0;
    }

    /**
     * This method gets the role type id of the role according to its name. It
     * primarily differs from calling getRoleType by checking for null values
     * and returning the default role if a null exists. Thus it provides some
     * protection against manually entered values into the url queries.
     *
     * @param roleTypeName The name of the role type to get the id for.
     * @return the id value of the role if it exists and 0 if it doesn't exist.
     */
    public int getRoleTypeId(String roleTypeName) {
        Role type = this.getRoleType(roleTypeName);
        if (type != null) {
            return type.getId();
        }
        return 0;
    }

    /**
     * This method gets the state type id of the state according to its name. It
     * primarily differs from calling getStateType by checking for null values
     * and returning the default state if a null exists. Thus it provides some
     * protection against manually entered values into the url queries.
     *
     * @param stateTypeName The name of the state type to get the id for.
     * @return the id value of the state if it exists and 0 if it doesn't exist.
     */
    public int getStateTypeId(String stateTypeName) {
        State type = this.getStateType(stateTypeName);
        if (type != null) {
            return type.getId();
        }
        return 0;
    }

    /**
     * This method gets the task action type id of the task action according to
     * its name. It primarily differs from calling getTaskActionType by checking
     * for null values and returning the default task action if a null exists.
     * Thus it provides some protection against manually entered values into the
     * url queries.
     *
     * @param taskActionTypeName The name of the task action type to get the id
     * for.
     * @return the id value of the task action if it exists and 0 if it doesn't
     * exist.
     */
    public int getTaskActionTypeId(String taskActionTypeName) {
        TaskAction type = this.getTaskActionType(taskActionTypeName);
        if (type != null) {
            return type.getId();
        }
        return 0;
    }

    /**
     * This method gets the resource type id of the resource according to its
     * name. It primarily differs from calling getResourceType by checking for
     * null values and returning the default resource if a null exists. Thus it
     * provides some protection against manually entered values into the url
     * queries.
     *
     * @param resourceTypeId The id of the resource type to get the name for.
     * @return the id value of the resource if it exists and an empty string if
     * it doesn't exist.
     */
    public String getResourceTypeName(int resourceTypeId) {
        ResourceType type = this.getResourceType(resourceTypeId);
        if (type != null) {
            return prepareName(type.getName());
        }
        return "";
    }

    /**
     * This method gets the action type id of the action according to its name.
     * It primarily differs from calling getActionType by checking for null
     * values and returning the default action if a null exists. Thus it
     * provides some protection against manually entered values into the url
     * queries.
     *
     * @param actionTypeId The id of the action type to get the name for.
     * @return the id value of the action if it exists and an empty string if it
     * doesn't exist.
     */
    public String getActionTypeName(int actionTypeId) {
        Action type = this.getActionType(actionTypeId);
        if (type != null) {
            return prepareName(type.getName());
        }
        return "";
    }

    /**
     * This method gets the role type id of the role according to its name. It
     * primarily differs from calling getRoleType by checking for null values
     * and returning the default role if a null exists. Thus it provides some
     * protection against manually entered values into the url queries.
     *
     * @param roleTypeId The id of the role type to get the name for.
     * @return the id value of the role if it exists and an empty string if it
     * doesn't exist.
     */
    public String getRoleTypeName(int roleTypeId) {
        Role type = this.getRoleType(roleTypeId);
        if (type != null) {
            return prepareName(type.getName());
        }
        return "";
    }

    /**
     * This method gets the task action type id of the task action according to
     * its name. It primarily differs from calling getTaskActionType by checking
     * for null values and returning the default task action if a null exists.
     * Thus it provides some protection against manually entered values into the
     * url queries.
     *
     * @param taskActionId The id of the task action type to get the name for.
     * @return the id value of the task action if it exists and an empty string
     * if it doesn't exist.
     */
    public String getTaskActionTypeName(int taskActionId) {
        TaskAction type = this.getTaskActionType(taskActionId);
        if (type != null) {
            return prepareName(type.getName());
        }
        return "";
    }

    /**
     * This method gets the state type id of the state according to its name. It
     * primarily differs from calling getStateType by checking for null values
     * and returning the default state if a null exists. Thus it provides some
     * protection against manually entered values into the url queries.
     *
     * @param stateTypeId The id of the state type to get the name for.
     * @return the id value of the state if it exists and an empty string if it
     * doesn't exist.
     */
    public String getStateTypeName(int stateTypeId) {
        State type = this.getStateType(stateTypeId);
        if (type != null) {
            return prepareName(type.getName());
        }
        return "";
    }

    /**
     * This method returns the Action.actionId of the task action.
     *
     * @param taskActionId the task action to get the action id of.
     * @return the action id if the task action exists or 0 if not.
     */
    public int getTaskActionActionId(int taskActionId) {
        TaskAction ta = this.getTaskActionType(taskActionId);
        if (ta != null) {
            return ta.getActionTypeId();
        }
        return 0;
    }

    /**
     * This method gets the task action according to the provided actionId and
     * resourceTypeId.For combination actions such as Add and Remove, this
     * method checks the resource type id to determine the proper task action,
     * otherwise just selects the task action according to the actionId and the
     * resourceId equaling the default id of resource.
     *
     * @param actionId the id of the action
     * @param resourceTypeId the id of the resource
     * @return
     */
    public int getTaskActionId(int actionId, int resourceTypeId) {
        int resourceId = this.getResourceTypeId("resource");
        for (TaskAction ta : this.taskActionTypes.values()) {
            if (ta.getActionTypeId() == actionId && (ta.getResourceTypeId() == resourceTypeId || ta.getResourceTypeId() == resourceId)) {
                return ta.getId();
            }
        }
        return 0;
    }

    /**
     * This method prepares the name for use as a key. Primarily it lowercases
     * the name and removes any spaces between words.
     *
     * @param name
     * @return
     */
    private String prepareName(String name) {
        return name.toLowerCase().replaceAll(" ", "");
    }
}
